/************************************************************************

								Heroes IV
					  Copyright 2000, The 3DO Company

	------------------------------------------------------------------
  						 abventure_spell.cpp

	$Header: /heroes4/adventure_spell.cpp $

	$NoKeywords: $

 ************************************************************************/

// kill the 255+ character identifier warning.  Templates tend to exceed that easily.
#include "precompiled.h"
#pragma warning( disable: 4786 )

#include "adventure_frame.h"

#include "adventure_events_spells_effect.h"
#include "adventure_map.h"
#include "adventure_map_window.h"
#include "adventure_spell_effect.h"
#include "adventure_spell_targeter.h"
#include "animation_cache.h"
#include "army.h"
#include "army_array.h"
#include "artifact_prop_spell_charges.h"
#include "combat_sounds.h"
#include "creature_array.h"
#include "creature_traits.h"
#include "dialog_adv_spell_target.h"
#include "direction.h"
#include "enum_operations.h"
#include "external_string.h"
#include "hero.h"
#include "music.h"
#include "player.h"
#include "random.h"
#include "replace_keywords.h"
#include "simple_dialog.h"
#include "sound.h"
#include "spellbook_window.h"
#include "spell_properties.h"
#include "town.h"

// -----------------------------------------------------------------
// -----------------------------------------------------------------
namespace
{
	// -----------------------------------------------------------------
	// -----------------------------------------------------------------
	bool fail_to_cast_spell( std::string const& message, t_spellbook_window_data const& spell_data )
	{
		ok_dialog( replace_keywords( message, "%spell_name", get_spell_name( spell_data.spell ) ), true );
		return false;
	}

	// -----------------------------------------------------------------
	// -----------------------------------------------------------------
	t_army * find_nearest_owned_boat(
		t_adventure_map &				map,
		int								owner_number,
		t_level_map_point_2d const &	point )
	{
		t_army * result = 0;
		int nearest_distance;
		int nearest_level_distance;

		t_army_list::iterator army_end = map.get_armies_end();
		t_army_list::iterator army_iter = map.get_armies_begin();
		for ( ; army_iter != army_end; ++army_iter )
		{
			t_army * army_ptr = army_iter->get();
			if (	!army_ptr->is_boat()
				||	army_ptr->get_owner_number() != owner_number
				||	!army_ptr->get_creatures().empty() )
				continue;

			t_level_map_point_2d delta = army_ptr->get_position() - point;
			int distance = delta.column * delta.column + delta.row * delta.row;
			int level_distance = delta.level >= 0 ? delta.level : -delta.level;

			if (	result == 0
				||	level_distance < nearest_level_distance
				||	( level_distance == nearest_level_distance && distance < nearest_distance ) )
			{
				result = army_ptr;
				nearest_distance = distance;
				nearest_level_distance = level_distance;
			}
		}

		return result;
	}

	// -----------------------------------------------------------------
	// pay cost of a spell
	// -----------------------------------------------------------------
	void pay_cost( t_creature_stack& caster, t_spellbook_window_data const& spell_data )
	{
		if (!spell_data.is_artifact)
		{
			t_hero* hero = caster.get_hero();
			int     cost = caster.get_spell_cost( spell_data.spell );
			int     points = hero->get_spell_points();

			hero->set_spell_points( points - cost );
			return;
		}

		if (spell_data.slot == k_artifact_slot_none)
		{
			assert( spell_data.backpack_slot >= 0);
			caster.remove_backpack( spell_data.backpack_slot );
			return;
		}

		t_artifact const& artifact = caster.get_artifact( spell_data.slot );

		t_artifact_effect_list const&	  effects = artifact.get_effects();
		t_artifact_effect*                effect;
		t_artifact_prop::t_spell_charges* charges;
		int								  effect_number;
		t_spell							  spell;

		for (effect_number = effects.size() - 1; effect_number >= 0; effect_number--)
		{
			effect = effects[effect_number];
			if (effect->get_effect() != k_artifact_effect_spell_charges)
				continue;
			charges = dynamic_cast<t_artifact_prop::t_spell_charges*>(effect);
			if (charges == 0)
				continue;

			spell = charges->get_spell();
			if (spell != spell_data.spell)
				continue;

			charges->use_spell_points( get_spell_cost( spell_data.spell ));
			return;
		}
		assert( false );
	}

	// -----------------------------------------------------------------
	// -----------------------------------------------------------------
	void play_sound( t_spell spell )
	{
		assert( spell >= 0 && spell < k_spell_count );
		t_sound_ptr sound_ptr = get_combat_sound( spell );
		if ( sound_ptr.get() != 0 )
			sound_ptr->play( get_sound_volume() );
	}

	// -----------------------------------------------------------------
	// t_mire_targeter class - used to target the mire spell
	// -----------------------------------------------------------------

	class t_mire_targeter : public t_adventure_spell_targeter
	{
	public:
		t_mire_targeter(
			t_adventure_frame &				frame,
			t_creature_array &				caster_army,
			int								caster_slot,
			t_spellbook_window_data const &	spell_data );

		virtual void	cast_spell(
							t_adventure_map_window &	window,
							t_screen_point const &		point );
		virtual bool	is_valid_target(
							t_adventure_map_window const &	window,
							t_screen_point const &			point );

	private:
		static t_army *	get_army_ptr(
							t_adventure_map_window const &	window,
							t_screen_point const &			point );

		t_creature_array &		m_caster_army;
		int						m_caster_slot;
		t_adventure_frame &		m_frame;
		t_spellbook_window_data	m_spell_data;

		void	apply( t_army & target ) const;
		bool	is_targetable( t_army const & target ) const;
	};

	t_mire_targeter::t_mire_targeter(
		t_adventure_frame &				frame,
		t_creature_array &				caster_army,
		int								caster_slot,
		t_spellbook_window_data const &	spell_data )
		:	m_caster_army( caster_army ),
			m_caster_slot( caster_slot ),
			m_frame( frame ),
			m_spell_data( spell_data )
	{
		assert( m_spell_data.spell == k_spell_mire );
	}

	void t_mire_targeter::cast_spell(
		t_adventure_map_window &	window,
		t_screen_point const &		point )
	{
		t_army * army_ptr = get_army_ptr( window, point );
		if ( army_ptr != 0 )
			apply( *army_ptr );
	}

	bool t_mire_targeter::is_valid_target(
		t_adventure_map_window const &	window,
		t_screen_point const &			point )
	{
		t_army * army_ptr = get_army_ptr( window, point );
		return army_ptr != 0 && is_targetable( *army_ptr );
	}

	t_army * t_mire_targeter::get_army_ptr(
		t_adventure_map_window const &	window,
		t_screen_point const &			point )
	{
		t_adventure_object * object_ptr = window.get_object( point );
		if ( object_ptr == 0 )
			return 0;
		return dynamic_cast< t_army * >( object_ptr );
	}

	void t_mire_targeter::apply( t_army & target ) const
	{
		if ( !is_targetable( target ) )
			return;

		t_player const * owner = target.get_owner();
		t_creature_array & creatures = target.get_creatures();

		bool some_affected = false;
		bool some_resisted = false;

		int slot_num;
		for ( slot_num = 0; slot_num < t_creature_array::k_size; ++slot_num )
		{
			if (	creatures[ slot_num ].get_number() <= 0
				||	creatures[ slot_num ].is_spell_active( k_spell_mire ) )
				continue;

			if ( random( 100 ) >= creatures[ slot_num ].get_magic_resistance( owner, k_spell_mire ) )
			{
				creatures[ slot_num ].set_spell( k_spell_mire );
				some_affected = true;
			}
			else
				some_resisted = true;
		}

		if ( some_resisted )
		{
			t_counted_ptr< t_adv_magic_resistance_effect > effect_ptr( new t_adv_magic_resistance_effect( target ) );
			play_sound( k_spell_magic_resistance );
			effect_ptr->run_modal();

			// Replay code /////////////////////

			target.get_map()->record_adv_spell_effect( &target, k_spell_magic_resistance_event_id, true );

			///////////////////////////////////////////////
		}

		if ( some_affected )
		{
			t_counted_ptr< t_adv_mire_effect > effect_ptr( new t_adv_mire_effect( target ) );
			play_sound( k_spell_mire );
			effect_ptr->run_modal();

			// Replay code /////////////////////
			
			target.get_map()->record_adv_spell_effect( &target, k_spell_mire_event_id, true);
			
			///////////////////////////////////////////////
		}

		pay_cost( m_caster_army[ m_caster_slot ], m_spell_data );

		m_frame.update();
	}

	bool t_mire_targeter::is_targetable( t_army const & target ) const
	{
		if ( !same_team( target.get_owner(), m_caster_army.get_owner() ) )
		{
			int const k_mire_range = 15;

			t_level_map_point_2d target_pos = target.get_position();
			t_level_map_point_2d caster_pos = m_caster_army.get_position();
			if ( target_pos.level == caster_pos.level && distance( target_pos - caster_pos ) <= k_mire_range )
			{
				t_player const * owner = target.get_owner();
				t_creature_array const & creatures = target.get_creatures();

				int slot_num;
				for ( slot_num = 0; slot_num < t_creature_array::k_size; ++slot_num )
				{
					if (	creatures[ slot_num ].get_number() > 0
						&&	!creatures[ slot_num ].is_spell_active( k_spell_mire )
						&&	creatures[ slot_num ].get_magic_resistance( owner, k_spell_mire ) < 100 )
						return true;
				}
			}
		}
		return false;
	}

}

// -----------------------------------------------------------------
// -----------------------------------------------------------------
static t_external_string const k_text_no_spell_targets( "no_spell_targets.misc" );

bool t_adventure_frame::cast_endurance( t_creature_array&			   army,
									    int							   caster_slot,
										t_spellbook_window_data const& spell_data )
{
	assert( spell_data.spell == k_spell_endurance );

	int const k_endurance_bonus = 300;

	std::vector< t_creature_stack * > target_ptrs;

	int i;
	for ( i = 0; i < t_creature_array::k_size; ++i )
	{
		if ( army[ i ].get_number() > 0 && !army[ i ].is_dead() && !army[ i ].is_spell_active( k_spell_endurance ) )
			target_ptrs.push_back( &army[ i ] );
	}

	if ( target_ptrs.empty() )
		return fail_to_cast_spell( k_text_no_spell_targets, spell_data );

	std::vector< t_creature_stack * >::const_iterator target_ptr_end = target_ptrs.end();
	std::vector< t_creature_stack * >::const_iterator target_ptr_iter = target_ptrs.begin();
	for ( ; target_ptr_iter != target_ptr_end; ++target_ptr_iter )
	{
		t_creature_stack & target = **target_ptr_iter;
		target.set_spell( k_spell_endurance );
		target.change_raw_adventure_movement( k_endurance_bonus );
	}

	pay_cost( army[ caster_slot ], spell_data );
	play_sound( k_spell_endurance );
	if ( &army == m_selected_army.get() )
	{
		reset_pathfinder();
		refresh_path();
	}
	update();

	return true;
}

// -----------------------------------------------------------------
// begin casting a healing spell
// -----------------------------------------------------------------
static t_external_string const k_text_no_healing( "no_healing.misc" );

bool t_adventure_frame::cast_healing( t_creature_array&				 army, 
		                              int							 caster_slot,
		                              t_spellbook_window_data const& spell_data )
{
	t_creature_stack&    caster = army[ caster_slot ];

	// find eligible targets
	int                  i;
	std::vector<t_hero*> targets;

	for (i = 0; i < t_creature_array::k_size; i++)
		if (army[i].get_number() > 0 && !army[i].is_dead() && army[i].get_wounds() > 0)
			targets.push_back( army[i].get_hero() );

	if (targets.empty())
		return fail_to_cast_spell( k_text_no_healing, spell_data );

	t_hero* target = 0;

	if (targets.size() == 1)
		target = targets.front();
	else
	{
		t_counted_ptr<t_dialog_healing_target> dialog;

		dialog = new t_dialog_healing_target( this, targets, spell_data.spell );
		if (!dialog->run_modal())
			return false;
		target = dialog->get_target();
	}

	int			power = caster.get_spell_power( spell_data.spell, army.get_grail_data() );
	int			wounds = target->get_wounds();

	if (spell_data.spell == k_spell_regeneration)
		power = wounds;
	if (wounds < power)
		power = wounds;
	wounds -= power;
	target->set_wounds( wounds );
	pay_cost( caster, spell_data );
	play_sound( spell_data.spell );
	update();

	return true;
}

// -----------------------------------------------------------------
// begin casting mana spell
// -----------------------------------------------------------------
static t_external_string const k_text_no_mana( "no_mana.misc" );

bool t_adventure_frame::cast_mana( t_creature_array&			  army, 
		                           int							  caster_slot,
		                           t_spellbook_window_data const& spell_data )
{
	assert( spell_data.spell == k_spell_mana );

	t_creature_stack&    caster = army[ caster_slot ];

	// find eligible targets
	int                  i;
	std::vector<t_hero*> targets;

	for (i = 0; i < t_creature_array::k_size; i++)
	{
		if ( army[i].get_number() <= 0 || army[i].is_dead() )
			continue;
		t_hero* hero = army[i].get_hero();
		if (hero != 0)
			targets.push_back( hero );
	}

	if (targets.empty())
		return fail_to_cast_spell( k_text_no_mana, spell_data );

	t_hero* target = 0;

	if (targets.size() == 1)
		target = targets.front();
	else
	{
		t_counted_ptr<t_dialog_mana_target> dialog;

		dialog = new t_dialog_mana_target( this, targets, spell_data.spell );
		if (!dialog->run_modal())
			return false;
		target = dialog->get_target();
	}

	int	power = ::get_basic_spell_power( k_spell_mana, 0, 100 );
	int spell_points = target->get_spell_points() + power;

	target->set_spell_points( spell_points );
	pay_cost( caster, spell_data );
	play_sound( k_spell_mana );
	update();

	return true;
}

// -----------------------------------------------------------------
// begin casting a healing spell
// -----------------------------------------------------------------
bool t_adventure_frame::cast_mass_healing( t_creature_array&			  army, 
		                                   int							  caster_slot,
		                                   t_spellbook_window_data const& spell_data )
{
	t_creature_stack&    caster = army[ caster_slot ];

	// find eligible targets
	int                  i;
	std::vector<t_hero*> targets;

	for (i = 0; i < t_creature_array::k_size; i++)
		if (army[i].get_number() > 0 && army[i].get_wounds() > 0)
			targets.push_back( army[i].get_hero() );

	if (targets.empty())
		return fail_to_cast_spell( k_text_no_healing, spell_data );

	int			power = caster.get_spell_power( spell_data.spell, army.get_grail_data() );
	int			wounds;
	t_hero*     target;

	pay_cost( caster, spell_data );
	play_sound( k_spell_heal );
	
	for (i = 0; i < targets.size(); i++)
	{
		target = targets[i];
		if (target->is_dead())
			continue;
		wounds = target->get_wounds();
		if (wounds < power)
			wounds = 0;
		else
			wounds -= power;
		target->set_wounds( wounds );
	}
	update();

	return true;
}

// -----------------------------------------------------------------
// -----------------------------------------------------------------
static t_external_string const k_text_not_adjacent_to_water( "not_adjacent_to_water.misc" );
static t_external_string const k_text_no_boats_available( "no_boats_available.misc" );

bool t_adventure_frame::cast_summon_boat( t_creature_array&				 army,
										  int							 caster_slot,
										  t_spellbook_window_data const& spell_data )
{
	assert( spell_data.spell == k_spell_summon_boat );

	// Only works for armies on the adventure map
	t_army * adv_army_ptr = army.get_army();
	if ( adv_army_ptr == 0 )
		return fail_to_cast_spell( k_text_not_adjacent_to_water, spell_data );

	t_army * boat_ptr = find_nearest_owned_boat( *m_adventure_map, adv_army_ptr->get_owner_number(), army.get_position() );
	if ( boat_ptr == 0 )
		return fail_to_cast_spell( k_text_no_boats_available, spell_data );

	t_adv_map_point water_point;
	if ( !adv_army_ptr->find_adjacent_space( water_point, *boat_ptr ) )
		return fail_to_cast_spell( k_text_not_adjacent_to_water, spell_data );

	pay_cost( army[ caster_slot ], spell_data );
	play_sound( k_spell_summon_boat );
	
	boat_ptr->teleport( water_point );
	if ( adv_army_ptr == m_selected_army.get() )
		reset_pathfinder();

	update();

	return true;
}

bool t_adventure_frame::ai_cast_summon_boat( t_creature_array& army, t_hero& hero, t_adv_map_point const& point )
{
	// Only works for armies on the adventure map
	t_army * adv_army_ptr = army.get_army();
	t_army * boat_ptr = find_nearest_owned_boat( *m_adventure_map, adv_army_ptr->get_owner_number(), point );
	if ( boat_ptr == 0 )
		return false;

	int cost = hero.get_spell_cost( k_spell_summon_boat );
	int points = hero.get_spell_points();

	hero.set_spell_points( points - cost );
	
	boat_ptr->teleport( point );

	return true;
}

// -----------------------------------------------------------------
// cast an adventure spell
// -----------------------------------------------------------------
bool t_adventure_frame::cast_targeted_adventure_spell( t_creature_array&			  army, 
													   int							  caster_slot,
													   t_spellbook_window_data const& spell_data )
{
	t_creature_stack&    caster = army[ caster_slot ];

	// find eligible targets
	int						i;
	std::vector<t_hero*>	targets;
	t_spell					spell = translate_spell( spell_data.spell );

	// some specific spells are translated for various reasons
	for (i = 0; i < t_creature_array::k_size; i++)
	{
		if ( army[i].get_number() <= 0 || army[i].is_dead() 
			 || army[i].is_spell_active( spell ))
			continue;
		t_hero* hero = army[i].get_hero();
		if (hero != 0)
			targets.push_back( hero );
	}

	if (targets.empty())
		return fail_to_cast_spell( k_text_no_spell_targets, spell_data );

	t_hero* target = 0;

	if (targets.size() == 1)
		target = targets.front();
	else
	{
		t_counted_ptr<t_dialog_adv_spell_target> dialog;

		dialog = new t_dialog_adv_spell_target( this, targets, spell_data.spell );
		if (!dialog->run_modal())
			return false;
		target = dialog->get_target();
	}

	target->set_spell( spell );
	pay_cost( caster, spell_data );
	play_sound( spell );
	update();

	return true;
}

// -----------------------------------------------------------------
// -----------------------------------------------------------------
static t_external_string const k_text_cant_cast_in_town( "cant_cast_in_town.misc" );

bool t_adventure_frame::cast_mire( t_creature_array&			  army,
								   int							  caster_slot,
								   t_spellbook_window_data const& spell_data,
								   t_town*						  town )
{
	assert( spell_data.spell == k_spell_mire );

	if ( town != 0 )
		return fail_to_cast_spell( k_text_cant_cast_in_town, spell_data );

	m_adventure_map_window->set_spell_targeter( new t_mire_targeter( *this, army, caster_slot, spell_data ) );

	return true;
}

// -----------------------------------------------------------------
// -----------------------------------------------------------------
extern t_external_string const k_text_spells_no_town;
extern t_external_string const k_text_spells_town_blocked;
static t_external_string const k_text_spells_too_many_armies( "too_many_armies.spells" );

bool t_adventure_frame::cast_town_gate( t_creature_array&			   army,
									    int							   caster_slot,
										t_spellbook_window_data const& spell_data,
										t_town*						   town )
{
	assert( spell_data.spell == k_spell_town_gate );

	if ( town != 0 )
		return fail_to_cast_spell(k_text_cant_cast_in_town, spell_data );

	t_player * owner_ptr = army.get_owner();
	if ( owner_ptr == 0 )
		return fail_to_cast_spell( k_text_spells_no_town, spell_data );

	t_adv_map_point target_position;
	bool blocked_town;
	
	t_town * dest_town_ptr
		= find_nearest_town( get_map(), owner_ptr, army.get_position(), target_position, &army, 
							 town, NULL, &blocked_town, true, 0 );

	if ( dest_town_ptr == 0 )
	{
		// Cast even if it will end the army in combat
		dest_town_ptr = find_nearest_town( get_map(), owner_ptr, army.get_position(),
										   target_position, &army, town, NULL, 
										   &blocked_town, false, 0 );

		if ( dest_town_ptr == 0 )
		{
			if ( blocked_town )
			{
				return fail_to_cast_spell( k_text_spells_town_blocked, spell_data );
			}
			else
			{
				return fail_to_cast_spell( k_text_spells_no_town, spell_data );
			}
		}
	}

	// If specified army is an 'adventure army' rather than say a town garrison,
	// simply move the army, otherwise, create a new army and place all of the
	// stacks into it
	t_army * adv_army_ptr = army.get_army();
	if ( adv_army_ptr != 0 )
	{
		adv_army_ptr->move( target_position );

		// Since find_nearest_town always looks for land spots, we can't be a boat anymore
		adv_army_ptr->set_boat_type( k_town_none );

		// If this is the currently selected army, center the view over the new location
		if ( adv_army_ptr == m_selected_army.get() )
		{
			m_adventure_map->display_selection( adv_army_ptr );
			m_adventure_map_window->center_view( target_position );
			reset_pathfinder();
		}
	}
	else
	{
		// Can't create a new army if the owner already owns the maximum
		// number of armies.
		if ( owner_ptr->get_armies()->size() >= k_player_army_limit )
			return fail_to_cast_spell( k_text_spells_too_many_armies, spell_data );

		t_army_ptr new_army_ptr( new t_army( &army )  );

		new_army_ptr->place( *m_adventure_map, target_position );
		update_army( new_army_ptr.get() );
		adv_army_ptr = new_army_ptr;
	}

	pay_cost( army[ caster_slot ], spell_data );
	play_sound( k_spell_town_gate );
	update();

	if ( adv_army_ptr )
	{
		// Visit the town but don't display messages
		adv_army_ptr->visit_town( dest_town_ptr );
		dest_town_ptr->check_bonus_buildings( adv_army_ptr, NULL );

		// Trigger events on the tile we just landed on
		adv_army_ptr->trigger_events();
	}

	return true;
}

// -----------------------------------------------------------------
// -----------------------------------------------------------------
bool t_adventure_frame::cast_pathfinding( t_creature_array&				 army,
										  int							 caster_slot,
										  t_spellbook_window_data const& spell_data )
{
	assert( spell_data.spell == k_spell_pathfinding );

	int slot_num;
	for ( slot_num = 0; true; ++slot_num )
	{
		if ( slot_num >= t_creature_array::k_size )
			return fail_to_cast_spell( k_text_no_spell_targets, spell_data );

		if ( army[ slot_num ].get_number() > 0 && !army[ slot_num ].is_spell_active( k_spell_pathfinding ) )
			break;
	}

	for ( ; slot_num < t_creature_array::k_size; ++slot_num )
	{
		if ( army[ slot_num ].get_number() > 0 )
			army[ slot_num ].set_spell( k_spell_pathfinding );
	}

	pay_cost( army[ caster_slot ], spell_data );
	play_sound( k_spell_pathfinding );
	if ( &army == m_selected_army.get() )
		reset_pathfinder();

	update();

	return true;
}

// -----------------------------------------------------------------
// -----------------------------------------------------------------
static t_external_string const k_text_precognition_message( "precognition_message.misc" );

bool t_adventure_frame::cast_visions( t_creature_array&				 army,
									  int							 caster_slot,
									  t_spellbook_window_data const& spell_data )
{
	assert( spell_data.spell == k_spell_visions );

	t_creature_stack & caster = army[ caster_slot ];
	caster.set_spell( k_spell_visions );

	pay_cost( caster, spell_data );
	play_sound( spell_data.spell );
	update();

	return true;
}

// -----------------------------------------------------------------
// begin casting an adventure spell
// -----------------------------------------------------------------
bool t_adventure_frame::cast_adventure_spell( t_creature_array&					army, 
											  int								caster_slot,
											  t_spellbook_window_data const&	spell_data,
											  t_town*							town )
{
	switch (spell_data.spell)
	{
		case k_spell_bind_wound:
		case k_spell_heal:
		case k_spell_regeneration:
		case k_spell_simple_healing:
			return cast_healing( army, caster_slot, spell_data );

		case k_spell_divine_intervention:
		case k_spell_mass_healing:
			return cast_mass_healing( army, caster_slot, spell_data );

		case k_spell_mana:
			return cast_mana( army, caster_slot, spell_data );

		case k_spell_freezing_attack:
		case k_spell_health_boost:
		case k_spell_immortality:
		case k_spell_luck_boost:
		case k_spell_morale_boost:
		case k_spell_speed:
		case k_spell_strength:
			return cast_targeted_adventure_spell( army, caster_slot, spell_data );

		case k_spell_summon_boat:
			return cast_summon_boat( army, caster_slot, spell_data );

		case k_spell_town_gate:
			return cast_town_gate( army, caster_slot, spell_data, town );

		case k_spell_mire:
			return cast_mire( army, caster_slot, spell_data, town );

		case k_spell_pathfinding:
			return cast_pathfinding( army, caster_slot, spell_data );

		case k_spell_visions:
			return cast_visions( army, caster_slot, spell_data );

		case k_spell_endurance:
			return cast_endurance( army, caster_slot, spell_data );
	}

	return false;
}
